package kube

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/pkg/errors"
	"go.uber.org/zap"
	bkeaddon "gopkg.openfuyao.cn/bkecommon/cluster/addon"
	corev1 "k8s.io/api/core/v1"
	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
	"sigs.k8s.io/cluster-api/controllers/remote"
	"sigs.k8s.io/cluster-api/util"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
)

type RemoteKubeClient interface {
	// InstallAddon install addon to remote cluster
	InstallAddon(bkeCluster *bkev1beta1.BKECluster, addonT *bkeaddon.AddonTransfer, addonRecorder *AddonRecorder) error
	// ApplyYaml apply yaml to remote cluster
	ApplyYaml(task *Task) error
	// NewK8sToken create a new admin K8S token for remote cluster
	NewK8sToken() (string, error)
	// KubeClient return kubernetes clientset and dynamic client
	KubeClient() (*kubernetes.Clientset, dynamic.Interface)
	// Collect remote cluster info
	Collect() (*CollectResult, []error, []error)
	// CheckClusterHealth check cluster health
	CheckClusterHealth(cluster *bkev1beta1.BKECluster, version string) error
	// NodeHealthCheck check single node health
	NodeHealthCheck(node *corev1.Node, expectVersion string, log *zap.SugaredLogger) error
	// CheckComponentHealth check components health
	CheckComponentHealth(node *corev1.Node) error
	// ListNodes list nodes
	ListNodes(option *metav1.ListOptions) (*corev1.NodeList, error)
	// GetPod get pod
	GetPod(namespace, name string) (*corev1.Pod, error)
	// SetLogger set logger
	SetLogger(logger *zap.SugaredLogger)
	// SetBKELogger set bke logger
	SetBKELogger(bkeLog *bkev1beta1.BKELogger)
}

type Client struct {
	ClientSet     *kubernetes.Clientset
	DynamicClient dynamic.Interface
	RestConfig    *rest.Config
	Log           *zap.SugaredLogger
	BKELog        *bkev1beta1.BKELogger
	Ctx           context.Context
}

var addToScheme sync.Once

// 这里有很多生成client的方法，但是几乎适配了任意场景

// NewRemoteClusterClient
// 从集群检索kubeconfig或者token，生成clinet.Client
func NewRemoteClusterClient(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (client.Client, error) {
	errs := []error{}
	// try use kubeconfig generate restconfig
	restConfig, err := remote.RESTConfig(ctx, "cluster-cache-tracker", c, util.ObjectKey(bkeCluster))
	if err != nil {
		errs = append(errs, err)
	}
	if len(errs) != 0 && bkeCluster.Spec.ControlPlaneEndpoint.IsValid() {
		restConfig, err = getRestConfigByToken(ctx, c, bkeCluster)
		if err != nil {
			errs = append(errs, err)
		}
	} else {
		errs = append(errs, errors.Errorf("failed use k8s token create remote cluster client, BKECluster %q controlPlaneEndpoint is invalid", utils.ClientObjNS(bkeCluster)))
	}
	ret, err := client.New(restConfig, client.Options{Scheme: c.Scheme()})
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create client for Cluster %s/%s", bkeCluster.Namespace, bkeCluster.Name)
	}
	return ret, nil
}

// NewClientFromRestConfig
// 从rest.Config生成RemoteKubeClient
func NewClientFromRestConfig(ctx context.Context, config *rest.Config) (RemoteKubeClient, error) {
	clientSet, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create cluster clientset")
	}
	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create remote cluster dynamicClient")
	}
	addToScheme.Do(func() {
		if err := apiextv1.AddToScheme(scheme.Scheme); err != nil {
			// This should never happen.
			panic(err)
		}
		if err := apiextv1beta1.AddToScheme(scheme.Scheme); err != nil {
			panic(err)
		}
	})
	return &Client{
		ClientSet:     clientSet,
		DynamicClient: dynamicClient,
		RestConfig:    config,
		Log:           log.SugareLogger,
		Ctx:           ctx,
	}, nil
}

// NewRemoteClientByCluster
// 使用cluster资源的信息从集群检索kubeconfig或者token，生成RemoteKubeClient
func NewRemoteClientByCluster(ctx context.Context, c client.Client, cluster *clusterv1.Cluster) (RemoteKubeClient, error) {
	config, err := remote.RESTConfig(ctx, "cluster-cache-tracker", c, util.ObjectKey(cluster))
	if err != nil {
		return nil, errors.Wrapf(err, "failed to get remote cluster %q config", cluster.Name)
	}
	return NewClientFromRestConfig(ctx, config)
}

// NewRemoteClientByBKECluster
// 使用bkecluster资源的信息从集群检索kubeconfig或者token，生成RemoteKubeClient
func NewRemoteClientByBKECluster(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (RemoteKubeClient, error) {
	errs := []error{}
	// try use kubeconfig generate restconfig
	config, err := remote.RESTConfig(ctx, "cluster-cache-tracker", c, util.ObjectKey(bkeCluster))
	if err != nil {
		errs = append(errs, err)
	}
	// try use token generate restconfig, if kubeconfig failed
	if len(errs) != 0 && bkeCluster.Spec.ControlPlaneEndpoint.IsValid() {
		config, err = getRestConfigByToken(ctx, c, bkeCluster)
		if err != nil {
			errs = append(errs, err)
		}
	} else {
		errs = append(errs, errors.Errorf("failed use k8s token create remote cluster client, BKECluster %q controlPlaneEndpoint is invalid", utils.ClientObjNS(bkeCluster)))
	}
	// all failed return error
	if len(errs) == 2 {
		return nil, kerrors.NewAggregate(errs)
	}

	return NewClientFromRestConfig(ctx, config)
}

// NewClientFromConfig
// 从kubeconfig文件生成RemoteKubeClient
func NewClientFromConfig(kubeConfigPath string) (RemoteKubeClient, error) {
	config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
	if err != nil {
		return nil, err
	}
	return NewClientFromRestConfig(context.Background(), config)
}

// NewClientFromKubeConfig
// 从kubeconfig文件内容生成RemoteKubeClient
func NewClientFromKubeConfig(kubeConfig []byte) (RemoteKubeClient, error) {
	config, err := clientcmd.RESTConfigFromKubeConfig(kubeConfig)
	if err != nil {
		return nil, err
	}
	return NewClientFromRestConfig(context.Background(), config)
}

// NewClientFromK8sToken
// 从token生成RemoteKubeClient
func NewClientFromK8sToken(host, port, token string) (RemoteKubeClient, error) {
	config := &rest.Config{
		BearerToken: token,
		Host:        fmt.Sprintf("https://%s:%s", host, port),
		TLSClientConfig: rest.TLSClientConfig{
			Insecure: true, // 设置为true时 不需要CA
			//CAData: []byte(c.CA),
		},
	}
	config.UserAgent = remote.DefaultClusterAPIUserAgent("cluster-cache-tracker")
	config.Timeout = 10 * time.Second
	return NewClientFromRestConfig(context.Background(), config)
}

func (c *Client) KubeClient() (*kubernetes.Clientset, dynamic.Interface) {
	return c.ClientSet, c.DynamicClient
}

func (c *Client) SetLogger(logger *zap.SugaredLogger) {
	c.Log = logger
}

func (c *Client) SetBKELogger(bkeLog *bkev1beta1.BKELogger) {
	c.BKELog = bkeLog
}

// GetTargetClusterClient returns target cluster client from bkeCluster
func GetTargetClusterClient(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*kubernetes.Clientset, dynamic.Interface, error) {
	cluster, err := util.GetOwnerCluster(ctx, c, bkeCluster.ObjectMeta)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "failed to get owner cluster for bkeCluster %q", bkeCluster.Name)
	}
	remoteClient, err := NewRemoteClientByCluster(ctx, c, cluster)
	if err != nil {
		return nil, nil, errors.Wrapf(err, "failed to create remote cluster %q client", cluster.Name)
	}
	cs, dc := remoteClient.KubeClient()
	return cs, dc, nil
}

// getRestConfigByToken get rest config by token
func getRestConfigByToken(ctx context.Context, c client.Client, bkeCluster *bkev1beta1.BKECluster) (*rest.Config, error) {
	secret, err := phaseutil.GetK8sTokenSecret(ctx, c, bkeCluster)
	if err != nil {
		return nil, err
	}
	token, ok := secret.Data["token"]
	if !ok || string(token) == "" {
		return nil, errors.Errorf("token data in secret %q not found", utils.ClientObjNS(secret))
	}

	config := &rest.Config{
		Host:            fmt.Sprintf("https://%s", bkeCluster.Spec.ControlPlaneEndpoint.String()),
		BearerToken:     string(token),
		BearerTokenFile: "",
		TLSClientConfig: rest.TLSClientConfig{
			Insecure: true,
		},
	}
	//config.UserAgent = remote.DefaultClusterAPIUserAgent("cluster-cache-tracker")
	//config.Timeout = 10 * time.Second
	return config, nil
}
